/****************************************************************************
 * Copyright (c) 2015 Composent, Inc.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors: Scott Lewis - initial API and implementation
 *
 * SPDX-License-Identifier: EPL-2.0
 *****************************************************************************/
package org.eclipse.ecf.remoteserviceadmin.ui.endpoint;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.ecf.discovery.identity.IServiceID;
import org.eclipse.ecf.internal.remoteservices.ui.Activator;
import org.eclipse.ecf.internal.remoteservices.ui.DiscoveryComponent;
import org.eclipse.ecf.internal.remoteservices.ui.Messages;
import org.eclipse.ecf.osgi.services.remoteserviceadmin.EndpointDescription;
import org.eclipse.ecf.osgi.services.remoteserviceadmin.EndpointDescriptionReader;
import org.eclipse.ecf.osgi.services.remoteserviceadmin.IEndpointDescriptionLocator;
import org.eclipse.ecf.osgi.services.remoteserviceadmin.RemoteServiceAdmin;
import org.eclipse.ecf.osgi.services.remoteserviceadmin.RemoteServiceAdmin.ImportReference;
import org.eclipse.ecf.osgi.services.remoteserviceadmin.RemoteServiceAdmin.ImportRegistration;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.AbstractEndpointNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointAsyncInterfacesNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointConfigTypesNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointConnectTargetIDNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointContentProvider;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointDiscoveryGroupNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointFrameworkIDNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointGroupNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointHostGroupNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointIDNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointIntentsNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointInterfacesNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointNamespaceNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointPackageVersionNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointPropertyNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointRemoteServiceFilterNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointRemoteServiceIDNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointServiceIDNode;
import org.eclipse.ecf.remoteserviceadmin.ui.endpoint.model.EndpointTimestampNode;
import org.eclipse.ecf.remoteservices.ui.RSAImageRegistry;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.pde.internal.runtime.registry.RegistryBrowser;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.part.ViewPart;
import org.osgi.service.remoteserviceadmin.EndpointEvent;
import org.osgi.service.remoteserviceadmin.RemoteServiceAdminEvent;

/**
 * @since 3.3
 */
public class EndpointDiscoveryView extends ViewPart {

	public static final String ID_VIEW = "org.eclipse.ecf.remoteserviceadmin.ui.views.EndpointDiscoveryView"; //$NON-NLS-1$

	protected TreeViewer viewer;
	protected Action copyValueAction;
	protected Action copyNameAction;
	protected Action importAction;

	protected Action undiscoverAction;

	protected Action edefDiscoverAction;

	protected Clipboard clipboard;

	private DiscoveryComponent discovery;

	protected EndpointContentProvider contentProvider;

	public EndpointDiscoveryView() {
	}

	public void createPartControl(Composite parent) {
		this.discovery = DiscoveryComponent.getDefault();
		this.discovery.setView(this);

		IViewSite viewSite = getViewSite();
		this.contentProvider = createContentProvider(viewSite);

		viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
		viewer.setContentProvider(this.contentProvider);
		viewer.setLabelProvider(new WorkbenchLabelProvider());
		viewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS);
		viewer.setInput(viewSite);

		makeActions();
		hookContextMenu();
		contributeToActionBars();
		// setup clipboard
		clipboard = new Clipboard(viewer.getControl().getDisplay());
		getSite().setSelectionProvider(viewer);

		// Add any previously discovered endpoints
		viewer.getControl().getDisplay().asyncExec(new Runnable() {
			@Override
			public void run() {
				IEndpointDescriptionLocator locator = discovery.getEndpointDescriptionLocator();
				if (locator != null) {
					EndpointDescription[] eds = locator.getDiscoveredEndpoints();
					for (EndpointDescription ed : eds)
						handleEndpointDescription(EndpointEvent.ADDED, ed);
				}
			}
		});

		showServicesInRegistryBrowser();
	}

	protected EndpointContentProvider createContentProvider(IViewSite viewSite) {
		return new EndpointContentProvider(viewSite, Messages.EndpointDiscoveryView_ENDPOINT_ROOT_NAME);
	}

	private int previousRegistryBrowserGroupBy;

	private IViewPart findView(String viewId) {
		IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
		if (window != null) {
			IWorkbenchPage page = window.getActivePage();
			if (page != null)
				return page.findView(viewId); // $NON-NLS-1$
		}
		return null;
	}

	@Deprecated
	protected int showInRegistryBrowser(int groupBy) {
		// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=270684#c33
		try {
			IViewPart view = findView("org.eclipse.pde.runtime.RegistryBrowser"); //$NON-NLS-1$
			if (view != null) {
				RegistryBrowser registryBrowser = (RegistryBrowser) view;
				return (int) registryBrowser.getClass().getDeclaredMethod("showGroupBy", int.class) //$NON-NLS-1$
						.invoke(registryBrowser, RegistryBrowser.SERVICES);
			}
		} catch (Exception e) {
			logWarning("Could not show services in PDE Plugin view", e); //$NON-NLS-1$
		}
		return RegistryBrowser.BUNDLES;
	}

	@Deprecated
	protected void showServicesInRegistryBrowser() {
		try {
			this.previousRegistryBrowserGroupBy = showInRegistryBrowser(RegistryBrowser.SERVICES);
		} catch (Exception e) {
			logWarning("Could not find pde registry view", e); //$NON-NLS-1$
		} catch (NoClassDefFoundError e) {
			logWarning("Could not find pde registry view", e); //$NON-NLS-1$
		}
	}

	@Override
	public void dispose() {
		showInRegistryBrowser(previousRegistryBrowserGroupBy);
		super.dispose();
		viewer = null;
		contentProvider = null;
		if (discovery != null) {
			discovery.setView(null);
			discovery = null;
		}
		discoveredEndpointIds.clear();
	}

	protected void hookContextMenu() {
		MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
		menuMgr.setRemoveAllWhenShown(true);
		menuMgr.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager manager) {
				EndpointDiscoveryView.this.fillContextMenu(manager);
			}
		});
		Menu menu = menuMgr.createContextMenu(viewer.getControl());
		viewer.getControl().setMenu(menu);
		getSite().registerContextMenu(menuMgr, viewer);
	}

	protected void contributeToActionBars() {
		IActionBars bars = getViewSite().getActionBars();
		bars.getMenuManager().add(edefDiscoverAction);
		bars.getToolBarManager().add(edefDiscoverAction);
	}

	protected void fillContextMenu(IMenuManager manager) {
		ITreeSelection selection = (ITreeSelection) viewer.getSelection();
		if (selection != null) {
			Object e = selection.getFirstElement();
			if (e instanceof EndpointPropertyNode) {
				manager.add(copyNameAction);
				manager.add(copyValueAction);
			} else if (e instanceof EndpointNode) {
				EndpointNode edNode = (EndpointNode) e;
				ImportReference ir = edNode.getImportReference();
				if (ir == null) {
					manager.add(importAction);
					manager.add(undiscoverAction);
				}
			}
		}
	}

	protected void log(int level, String message, Throwable e) {
		Activator.getDefault().getLog().log(new Status(level, Activator.PLUGIN_ID, message, e));
	}

	protected void logWarning(String message, Throwable e) {
		log(IStatus.WARNING, message, e);
	}

	protected void logError(String message, Throwable e) {
		log(IStatus.ERROR, message, e);
	}

	protected void makeActions() {
		copyValueAction = new Action() {
			public void run() {
				Object o = ((ITreeSelection) viewer.getSelection()).getFirstElement();
				String data = ((EndpointPropertyNode) o).getPropertyValue().toString();
				if (data != null && data.length() > 0) {
					clipboard.setContents(new Object[] { data }, new Transfer[] { TextTransfer.getInstance() });
				}
			}
		};
		copyValueAction.setText(Messages.EndpointDiscoveryView_COPY_PROPERTY_VALUE);
		copyValueAction.setToolTipText(Messages.EndpointDiscoveryView_COPY_PROPERTY_VALUE);
		copyValueAction.setImageDescriptor(RSAImageRegistry.DESC_PROPERTY_OBJ);

		copyNameAction = new Action() {
			public void run() {
				Object o = ((ITreeSelection) viewer.getSelection()).getFirstElement();
				String data = ((EndpointPropertyNode) o).getPropertyName();
				if (data != null && data.length() > 0) {
					clipboard.setContents(new Object[] { data }, new Transfer[] { TextTransfer.getInstance() });
				}
			}
		};
		copyNameAction.setText(Messages.EndpointDiscoveryView_COPY_PROPERTY_NAME);
		copyNameAction.setToolTipText(Messages.EndpointDiscoveryView_COPY_PROPERTY_NAME);
		copyNameAction.setImageDescriptor(RSAImageRegistry.DESC_PROPERTY_OBJ);

		importAction = new Action() {
			public void run() {
				EndpointNode edNode = getEDNodeSelected();
				if (edNode != null) {
					RemoteServiceAdmin rsa = discovery.getRSA();
					if (rsa == null)
						showMessage(Messages.EndpointDiscoveryView_ERROR_MSG_RSA_IS_NULL);
					else {
						// Do import
						EndpointDescription ed = edNode.getEndpointDescription();
						ImportRegistration reg = (ImportRegistration) rsa.importService(ed);
						if (reg == null) {
							logError(Messages.EndpointDiscoveryView_ERROR_MSG_RSA_IMPORTSERVICE_FAILED,
									new Exception("Import Registration Is Null")); //$NON-NLS-1$
							showMessage(Messages.EndpointDiscoveryView_ERROR_MSG_RSA_IMPORTSERVICE_FAILED_PREFIX
									+ "Import Registration Is Null" //$NON-NLS-1$
									+ Messages.EndpointDiscoveryView_ERROR_MSG_SUFFIX);
							return;
						}
						// Check if import exception in returned registration
						Throwable exception = reg.getException();
						if (exception != null) {
							logError(Messages.EndpointDiscoveryView_ERROR_MSG_RSA_IMPORTSERVICE_FAILED, exception);
							showMessage(Messages.EndpointDiscoveryView_ERROR_MSG_RSA_IMPORTSERVICE_FAILED_PREFIX
									+ exception.getMessage() + Messages.EndpointDiscoveryView_ERROR_MSG_SUFFIX);
						} else {
							// Success! Set registration
							// and refresh
							edNode.setImportReference((ImportReference) reg.getImportReference());
							viewer.refresh();
						}
					}
				}
			}
		};
		importAction.setText(Messages.EndpointDiscoveryView_IMPORT_REMOTE_SERVICE);
		importAction.setToolTipText(Messages.EndpointDiscoveryView_IMPORT_REMOTE_SERVICE_TT);
		importAction.setImageDescriptor(RSAImageRegistry.DESC_RSPROXY_CO);

		edefDiscoverAction = new Action() {
			public void run() {
				IEndpointDescriptionLocator locator = discovery.getEndpointDescriptionLocator();
				if (locator != null) {
					FileDialog dialog = new FileDialog(viewer.getControl().getShell(), SWT.OPEN);
					dialog.setFilterExtensions(new String[] { "*.xml" }); //$NON-NLS-1$
					dialog.setText(Messages.EndpointDiscoveryView_OPEN_EDEF_FILE);
					dialog.setFilterPath(null);
					String result = dialog.open();
					if (result != null)
						try {
							EndpointDescription[] eds = (EndpointDescription[]) new EndpointDescriptionReader()
									.readEndpointDescriptions(new FileInputStream(result));
							if (eds != null) {
								for (int i = 0; i < eds.length; i++)
									locator.discoverEndpoint(eds[i]);
							}
						} catch (IOException e) {
							logError(Messages.EndpointDiscoveryView_ERROR_MSG_ENDPOINT_PARSING_FAILED, e);
							showMessage(Messages.EndpointDiscoveryView_ERROR_MSG_ENDPOINT_PARSING_FAILED_PREFIX
									+ e.getMessage() + Messages.EndpointDiscoveryView_ERROR_MSG_SUFFIX);
						}
				}
			}
		};
		edefDiscoverAction.setText(Messages.EndpointDiscoveryView_OPEN_EDEF_FILE_DIALOG);
		edefDiscoverAction.setToolTipText(Messages.EndpointDiscoveryView_OPEN_EDEF_FILE_DIALOG_TT);
		edefDiscoverAction.setEnabled(discovery.getRSA() != null);
		edefDiscoverAction.setImageDescriptor(
				PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJ_FILE));

		undiscoverAction = new Action() {
			public void run() {
				EndpointNode endpoint = getEDNodeSelected();
				if (endpoint != null && endpoint.getImportReference() == null) {
					IEndpointDescriptionLocator l = discovery.getEndpointDescriptionLocator();
					if (l != null && MessageDialog.openQuestion(viewer.getControl().getShell(),
							Messages.EndpointDiscoveryView_REMOVE_ENDPOINT_QUESTION_TITLE,
							Messages.EndpointDiscoveryView_REMOVE_ENDPOINT_QUESTION))
						l.undiscoverEndpoint(endpoint.getEndpointDescription());

				}
			}
		};
		undiscoverAction.setText(Messages.EndpointDiscoveryView_REMOVE_ENDPOINT);
		undiscoverAction.setToolTipText(Messages.EndpointDiscoveryView_REMOVE_ENDPOINT_TT);
	}

	protected EndpointNode getEDNodeSelected() {
		AbstractEndpointNode aen = getNodeSelected();
		return (aen instanceof EndpointNode) ? (EndpointNode) aen : null;
	}

	boolean isRootSelected() {
		return contentProvider.getRootNode().equals(getNodeSelected());
	}

	protected AbstractEndpointNode getNodeSelected() {
		return ((AbstractEndpointNode) ((ITreeSelection) viewer.getSelection()).getFirstElement());
	}

	protected void showMessage(String message) {
		MessageDialog.openInformation(viewer.getControl().getShell(),
				Messages.EndpointDiscoveryView_ENDPOINT_MSGBOX_TITLE, message);
	}

	/**
	 * Passing the focus request to the viewer's control.
	 */
	public void setFocus() {
		viewer.getControl().setFocus();
	}

	protected void handleEndpointDescription(final int type, final EndpointDescription ed) {
		if (ed == null || viewer == null)
			return;
		viewer.getControl().getDisplay().asyncExec(new Runnable() {
			@Override
			public void run() {
				switch (type) {
				case EndpointEvent.ADDED:
					addEndpoint(ed);
					break;
				case EndpointEvent.REMOVED:
					removeEndpoint(ed);
					break;
				}
				viewer.setExpandedState(contentProvider.getRootNode(), true);
				viewer.refresh();
			}
		});
	}

	public void handleEndpointChanged(final EndpointEvent event) {
		handleEndpointDescription(event.getType(), (EndpointDescription) event.getEndpoint());
	}

	private List<String> discoveredEndpointIds = new ArrayList<String>();

	protected void addEndpoint(EndpointDescription ed) {
		if (EndpointDiscoveryView.this.previousRegistryBrowserGroupBy != RegistryBrowser.SERVICES)
			showServicesInRegistryBrowser();
		String edId = ed.getId();
		if (!discoveredEndpointIds.contains(edId)) {
			discoveredEndpointIds.add(edId);
			contentProvider.getRootNode().addChild(createEndpointDescriptionNode(ed));
		}
	}

	protected void removeEndpoint(EndpointDescription ed) {
		if (discoveredEndpointIds.remove(ed.getId()))
			contentProvider.getRootNode().removeChild(new EndpointNode(ed));
	}

	protected ImportReference findImportReference(EndpointDescription ed) {
		RemoteServiceAdmin rsa = discovery.getRSA();
		if (rsa == null)
			return null;
		List<ImportRegistration> iRegs = rsa.getImportedRegistrations();
		for (ImportRegistration ir : iRegs) {
			ImportReference importRef = (ImportReference) ir.getImportReference();
			if (importRef != null && ed.equals(importRef.getImportedEndpoint()))
				return importRef;
		}
		return null;
	}

	protected EndpointNode createEndpointDescriptionNode(EndpointDescription ed) {
		EndpointNode edo = new EndpointNode(ed, findImportReference(ed));

		// Interfaces
		EndpointInterfacesNode ein = new EndpointInterfacesNode();
		for (String intf : ed.getInterfaces())
			ein.addChild(new EndpointPackageVersionNode(EndpointNode.getPackageName(intf)));
		edo.addChild(ein);
		// Async Interfaces (if present)
		List<String> aintfs = ed.getAsyncInterfaces();
		if (aintfs.size() > 0) {
			EndpointAsyncInterfacesNode ain = new EndpointAsyncInterfacesNode();
			for (String intf : ed.getAsyncInterfaces())
				ain.addChild(new EndpointPackageVersionNode(EndpointNode.getPackageName(intf)));
			edo.addChild(ain);
		}
		// ID
		edo.addChild(new EndpointIDNode());
		// Remote Service Host
		EndpointHostGroupNode idp = new EndpointHostGroupNode(Messages.EndpointDiscoveryView_REMOTE_HOST_NAME);
		// Host children
		idp.addChild(new EndpointNamespaceNode());
		idp.addChild(new EndpointRemoteServiceIDNode());
		org.eclipse.ecf.core.identity.ID connectTarget = ed.getConnectTargetID();
		if (connectTarget != null)
			idp.addChild(new EndpointConnectTargetIDNode());
		idp.addChild(new EndpointServiceIDNode());
		idp.addChild(new EndpointIntentsNode());
		idp.addChild(new EndpointConfigTypesNode());
		idp.addChild(new EndpointFrameworkIDNode());
		idp.addChild(new EndpointTimestampNode());
		String filter = ed.getRemoteServiceFilter();
		if (filter != null)
			idp.addChild(new EndpointRemoteServiceFilterNode());
		edo.addChild(idp);

		IEndpointDescriptionLocator locator = discovery.getEndpointDescriptionLocator();
		IServiceID serviceID = (locator == null) ? null : locator.getNetworkDiscoveredServiceID(ed);

		if (serviceID != null)
			edo.addChild(
					new EndpointDiscoveryGroupNode(Messages.EndpointDiscoveryView_DISCOVERY_GROUP_NAME, serviceID));

		return edo;
	}

	EndpointNode findEndpointNode(final ImportReference ir) {
		EndpointGroupNode egn = contentProvider.getRootNode();
		for (AbstractEndpointNode aen : egn.getChildren()) {
			if (aen instanceof EndpointNode) {
				EndpointNode en = (EndpointNode) aen;
				ImportReference iRef = en.getImportReference();
				if (iRef != null && iRef.equals(ir))
					return en;
			}
		}
		return null;
	}

	public void handleRSAEent(RemoteServiceAdminEvent event) {
		final int type = event.getType();
		Throwable t = event.getException();
		if (t == null) {
			TreeViewer treeViewer = this.viewer;
			if (treeViewer == null)
				return;
			switch (type) {
			case RemoteServiceAdminEvent.IMPORT_UNREGISTRATION:
				final ImportReference ir = (ImportReference) event.getImportReference();
				if (ir != null) {
					treeViewer.getControl().getDisplay().asyncExec(new Runnable() {
						@Override
						public void run() {
							EndpointGroupNode egn = contentProvider.getRootNode();
							for (AbstractEndpointNode aen : egn.getChildren()) {
								if (aen instanceof EndpointNode) {
									EndpointNode en = (EndpointNode) aen;
									ImportReference iRef = en.getImportReference();
									if (iRef != null && iRef.equals(ir))
										en.setImportReference(null);
								}
							}
							viewer.refresh();
						}
					});
				}
				break;
			}
		}
	}

}